/** @file oal_workqueue.h
  *
  * @brief This file contains the workqueue functions for driver.
  *
  *  Copyright (C) 2022, Artosyn Technologies. ALL RIGHTS RESERVED.
  *
  *  This Information is proprietary to Artosyn Technologies and MAY NOT
  *  be copied by any method or incorporated into another program without
  *  the express written consent of Artosyn. This Information or any portion
  *  thereof remains the property of Artosyn. The Information contained herein
  *  is believed to be accurate and Artosyn assumes no responsibility or
  *  liability for its use in any way and conveys no license or title under
  *  any patent or copyright and makes no representation or warranty that this
  *  Information is free from patent or copyright infringement.
  *
  */

/****************************
 * Include
 ****************************/
#include "oal_workqueue.h"
#include "utils/utils.h"

/****************************
 * Function defines
 ****************************/
/**
 *  @brief Check the state of workqueue and call process func
 *
 *  @param work     A pointer to work_struct structure
 *
 *  @return         N/A
 */
static void
oal_work_func(struct work_struct *work)
{
    oal_workqueue *ar_workqueue;
    oal_handle *handle;
    unsigned long flags = 0;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    ar_workqueue = container_of(work, oal_workqueue, work);
    handle = ar_workqueue->handle;

    if (handle == NULL){
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is NULL!\n", __func__);
        goto exit_workqueue;
    }

    spin_lock_irqsave(&ar_workqueue->lock, flags);

    /* Check if workqueue already processing */
    if (ar_workqueue->state == OAL_WORKQUEUE_STATE_IDLE) {
        ar_workqueue->state = OAL_WORKQUEUE_STATE_RUN;
        spin_unlock_irqrestore(&ar_workqueue->lock, flags);
    } else {
        spin_unlock_irqrestore(&ar_workqueue->lock, flags);
        AR_DBG(DM_GENERIC, DL_WRN, "%s: workqueue is run or stop\n", __func__);
        goto exit_workqueue;
    }

    /* process wq func */
    if (ar_workqueue->ar_func) {
        ar_workqueue->ar_func(handle, ar_workqueue->priv);
    }

    spin_lock_irqsave(&ar_workqueue->lock, flags);
    ar_workqueue->state = OAL_WORKQUEUE_STATE_IDLE;
    spin_unlock_irqrestore(&ar_workqueue->lock, flags);

exit_workqueue:
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
}

/**
 *  @brief Init the workqueue and register the process func
 *
 *  @param handle           A pointer to oal_handle structure
 *  @param ar_workqueue     (OUT)A pointer to oal_workqueue structure
 *  @param name             Name of the workqueue
 *  @param flags            Flag of the workqueue
 *  @param ar_func          Process func
 *  @param priv             Private data
 *
 *  @return OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_init_workqueue(oal_handle *handle, oal_workqueue **ar_workqueue, const char *name, unsigned int flags, ar_work_func ar_func, void* priv)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    oal_workqueue *workq;

    workq = (*ar_workqueue) = kzalloc(sizeof(oal_workqueue), GFP_KERNEL);
    if (!workq) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: oal_workqueue alloc failed\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto error_init_workqueue;
    } else {
        workq->handle = handle;
    }

    workq->workqueue = alloc_workqueue(name, flags, 1);
    if (!workq->workqueue) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: workqueue alloc failed\n", __func__);
        kfree(workq);
        ret = OAL_STATUS_FAILURE;
        goto error_init_workqueue;
    } else {
        AR_DBG(DM_GENERIC, DL_MSG, "%s: %s workqueue=%p\n", __func__, name, workq->workqueue);
    }

    workq->state = OAL_WORKQUEUE_STATE_IDLE;

    OAL_INIT_WORK(&workq->work, oal_work_func);

    AR_DBG(DM_GENERIC, DL_INF, "%s: %s work=%p, work->func=%p\n", __func__, name, &workq->work, workq->work.func);

    workq->ar_func = ar_func;
    workq->priv = priv;

    spin_lock_init(&workq->lock);

error_init_workqueue:
    return ret;
}

/**
 *  @brief  Deinit the workqueue
 *
 *  @param  handle           A pointer to oal_handle structure
 *  @param  ar_workqueue     A pointer to oal_workqueue structure
 *
 *  @return N/A
 */
void
oal_deinit_workqueue(oal_handle *handle, oal_workqueue *ar_workqueue)
{
    if (ar_workqueue) {
        if (ar_workqueue->workqueue) {
            flush_workqueue(ar_workqueue->workqueue);
            destroy_workqueue(ar_workqueue->workqueue);
            ar_workqueue->workqueue = NULL;
        }

        kfree(ar_workqueue);
    }
}

/**
 *  @brief  queue_work the workqueue
 *
 *  @param  handle           A pointer to oal_handle structure
 *  @param  ar_workqueue     A pointer to oal_workqueue structure
 *
 *  @return N/A
 */
void
oal_queue_work(oal_handle *handle, oal_workqueue *ar_workqueue)
{
    unsigned long flags = 0;

    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is NULL\n", __func__);
        return;
    }

    spin_lock_irqsave(&handle->state_lock, flags);
    if (handle->state == AR_BUS_STATE_DOWN) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle state is down\n", __func__);
        spin_unlock_irqrestore(&handle->state_lock, flags);
        return;
    }
    spin_unlock_irqrestore(&handle->state_lock, flags);

    if (ar_workqueue && ar_workqueue->workqueue) {
        queue_work(ar_workqueue->workqueue, &ar_workqueue->work);
    } else {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: workqueue or ar_workqueue->workqueue is NULL\n", __func__);
    }
}

/**
 *  @brief  Stop the workqueue
 *
 *  @param  handle           A pointer to oal_handle structure
 *  @param  ar_workqueue     A pointer to oal_workqueue structure
 *
 *  @return N/A
 */
void
oal_stop_workqueue(oal_handle *handle, oal_workqueue *ar_workqueue)
{
    unsigned long flags = 0;

    spin_lock_irqsave(&ar_workqueue->lock, flags);
    ar_workqueue->state = OAL_WORKQUEUE_STATE_STOP;
    spin_unlock_irqrestore(&ar_workqueue->lock, flags);
}